package org.slusk.thynwor.neat;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.jgap.Chromosome;
import org.slusk.thynwor.corestructures.Environment;

import com.anji.hyperneat.nd.ActivatorND;
import com.anji.hyperneat.nd.HyperNEATFitnessFunctionND;
import com.anji.hyperneat.nd.HyperNeatTranscriberND;
import com.anji.integration.ActivatorTranscriber;
import com.anji.integration.TranscriberException;
import com.anji.neat.Evolver;
import com.anji.util.Properties;

public class SingleAgentTaskSubstrateFitnessFunction extends HyperNEATFitnessFunctionND {

	private static final long serialVersionUID = -5913552257257746198L;

	private static double thresholdForSerialization = 55;
	private static final String BASE_FILE_NAME_FOR_SERIALIZATION = "satsNet";
	private static final String DIRECTORY_FOR_SERIALIZATION = "serialized";
	private static final String sep;
	private static Properties props;
			
	private double fitnessRecord = -40000;
	private double performanceRecord = -1;
	private int subjectCount = 0;
	private int generationCount = 0;

	private static int simulationsToRun = 1;

	private static int timeSteps = 10;

	private static int maxFitnessValue = 1000;
	private static double foodCollectedWgt = 0.5;
	private static double moveFoodTowardGoalWgt = 0.2;
	private static double assistWithFoodWgt = 0.1;
	private static double attachedToFoodWgt = 0.05;
	private static double handsFullWgt = 0.05;
	private static double hitWallWgt = 0.1;
	
	private static double foodCollectedGoal = ((double)timeSteps) / 4;	// Return 1 food every 4 steps
	private static double moveFoodTowardGoalGoal = (double) timeSteps;	// any given turn, one bot is moving toward goal with food
	private static double assistWithFoodGoal = (double) timeSteps;
	private static double attachedToFoodGoal = (double) timeSteps;
	private static double handsFullGoal = (double) timeSteps;
	private static double hitWallGoal = (double) timeSteps;
	
	private static Logger logger;
	private static FileHandler fileTxt;
	private static SimpleFormatter formatterTxt;
	
	
	static {
		sep = System.getProperty("file.separator");
		
		// Create Logger
		logger = Logger.getLogger(SingleAgentTaskSubstrateFitnessFunction.class.getName());
		logger.setLevel(Level.INFO);
		try {
			fileTxt = new FileHandler("sats_"+ timeStamp() +".log");
			formatterTxt = new SimpleFormatter();
			fileTxt.setFormatter(formatterTxt);
			logger.addHandler(fileTxt);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	public SingleAgentTaskSubstrateFitnessFunction() {

	}
	

	@Override
	protected int evaluate(Chromosome genotype, ActivatorND substrate, int evalThreadIndex) {
		ArrayList<Double> fitnessArray = new ArrayList<Double>(simulationsToRun);
		ArrayList<Double> performanceArray = new ArrayList<Double>(simulationsToRun);
		double fitness = 0.0, performance = 0.0;
		
		for (int i = 0; i < simulationsToRun; i++) {
			Environment e = new Environment();
			e.setSatsNet(substrate);
			
			e.runSim(timeSteps);
			
			fitnessArray.add(calculateFitness(e));
			double foodCollected = ((double)e.getFoodCollected()) / foodCollectedGoal;
			performanceArray.add(foodCollected);
		}
		
		fitness = averageResults(fitnessArray);
		performance = averageResults(performanceArray);
		genotype.setPerformanceValue((float) performance);
		
		if (fitness > fitnessRecord) {
			logger.info("*** FITNESS RECORD: Generation: " + generationCount + ", Subject: " + subjectCount + ", performance: " + performance + ", fitnessRecord: " + fitness);
			fitnessRecord = fitness;
			
			if (fitness > thresholdForSerialization) {
				thresholdForSerialization += 150;
			}
		} 
		if (performance > performanceRecord) {
			logger.info("*** PERFORMANCE RECORD: Generation: " + generationCount + ", Subject: " + subjectCount + ", fitness: " + fitness + ", performanceRecord: " + performance);
			performanceRecord = performance;
		} 
	
		subjectCount++;
		
		return (int) Math.round(fitness * maxFitnessValue);
	}


	private double calculateFitness(Environment e) {
		
		double foodCollected = ((double)e.getFoodCollected()) / foodCollectedGoal * foodCollectedWgt;
		
		double moveFoodTowardGoal = ((double)e.getMoveFoodTowardGoal()) / moveFoodTowardGoalGoal * moveFoodTowardGoalWgt;
		
		double assistWithFood = ((double)e.getAssistWithFood()) / assistWithFoodGoal * assistWithFoodWgt;
		
		double attachedToFood = ((double)e.getAttachedToFood()) / attachedToFoodGoal * attachedToFoodWgt;
		
		// Penalties
		double handsFull = handsFullWgt - ((double)e.getHandsFull()) / handsFullGoal * handsFullWgt;
		double hitWall = hitWallWgt - ((double)e.getHitObstacle()) / hitWallGoal * hitWallWgt;

		double fitness = foodCollected
				+ moveFoodTowardGoal
				+ assistWithFood
				+ attachedToFood
				+ handsFull
				+ hitWall;
		
		fitness /= foodCollectedWgt + moveFoodTowardGoalWgt + assistWithFoodWgt + attachedToFoodWgt + handsFullWgt + hitWallWgt;
		
		if (fitness < 0) fitness = 0;
		if (fitness > 1) fitness = 1;
		
		return fitness;
	}

	public static double averageResults(ArrayList<Double> fitnessArray) {
		double result = 0;
		for (Double err : fitnessArray) {
			result += err;
		}
		return result / fitnessArray.size();
	}

	
	public int getMaxFitnessValue() {
		return maxFitnessValue;
	}

	public static void main( String[] args ) throws Throwable {
		try {
			if ( args.length != 1 ) {
				System.exit( -1 );
			}

			// Anji Properties class... file must be in classpath 
			Properties props = new Properties( args[ 0 ] );
            
            String runID = props.getProperty("run.name") + "-" + System.currentTimeMillis();
            String outputDir = props.getProperty("fitness_function.class") + File.separatorChar + runID + File.separatorChar;
            props.setProperty("run.id", ""+runID);
            props.setProperty("output.dir", outputDir);
            
            Evolver evolver = new Evolver();
            
            evolver.init( props );
            evolver.run();
            System.exit( 0 );
		}
		catch ( Throwable th ) {
			logger.log(Level.SEVERE, th.getMessage());
			throw th;
		}
	}

	public static String timeStamp() {
		Calendar calendar = Calendar.getInstance();
		StringBuilder sb = new StringBuilder();
		sb.append(calendar.get(Calendar.YEAR));
		sb.append(calendar.get(Calendar.MONTH));
		sb.append(calendar.get(Calendar.DAY_OF_MONTH));
		sb.append(".");
		sb.append(calendar.get(Calendar.HOUR_OF_DAY));
		sb.append(".");
		sb.append(calendar.get(Calendar.MINUTE));
		sb.append(".");
		sb.append(calendar.get(Calendar.SECOND));
		return sb.toString();
	}

	public boolean endRun() {
		return super.endRun();
	}

	public void dispose() {
		super.dispose();
	}

	@Override
	protected void scale(int scaleCount, int scaleFactor) {

	}	
}

